package com.mxx.common.netty.ws;

import com.mxx.common.netty.NettyWebsocketOperator;
import com.mxx.common.netty.annotation.NettyHandler;
import com.mxx.common.netty.annotation.Websocket;
import com.mxx.common.netty.exception.ExceptionCatcherByWebsocket;
import com.mxx.common.netty.exception.IllegalParameterException;
import com.mxx.common.netty.exception.IllegalPathDuplicatedException;
import com.mxx.common.netty.http.NettyHttpRequest;
import com.mxx.common.utils.ClassUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Auther: Mr. Zeng
 * @Date: 2020/7/17 17:27
 * @Description: 暂无描述
 */
@Slf4j
public class DefaultNettyWebsocketOperator implements ApplicationContextAware, NettyWebsocketOperator {
    /**
     * 路径与处理器的持有
     */
    private HashMap<String, NettWebsocketMethod> functionHandlerMap = new HashMap<>();

    private WebsocketFactory websocketFactory;

    private ExceptionCatcherByWebsocket exceptionCatcherByWebsocket;

    @Override
    public boolean open(ChannelHandlerContext context, NettyHttpRequest request) {
        NettyChannelSupervise.addChannel(context.channel());
        return true;
    }

    @Override
    public void close(ChannelHandlerContext ctx) {
        NettyChannelSupervise.removeChannel(ctx.channel());
    }

    @Override
    public void execute(ChannelHandlerContext ctx, WebSocketFrame webSocketFrame) throws InvocationTargetException, IllegalAccessException {
        try {
            NettyWebsocketRequest nettyNioRequest = websocketFactory.createRequest(ctx, webSocketFrame);
            NettyWebsocketMessage websocketMessage = nettyNioRequest.getNettyWebsocketMessage();
            NettWebsocketMethod processor = functionHandlerMap.get(websocketMessage.getMethod());
            if (processor != null) {
                processor.invoke(nettyNioRequest, websocketMessage);
            } else {
                log.error("websocket method {} ont found ", websocketMessage.getMethod());
            }
        } finally {
            while (webSocketFrame.refCnt() != 0) {
                ReferenceCountUtil.release(webSocketFrame);
            }
        }

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {

        Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(NettyHandler.class);
        for (Map.Entry<String, Object> entry : handlers.entrySet()) {
            Object source = entry.getValue();
            List<Method> methodList = ClassUtil.getMethodListWithAnnotation(source.getClass(), Websocket.class);
            for (Method method : methodList) {
                Parameter[] parameter = method.getParameters();

                if (parameter.length != 2 || !(ClassUtil.isDomainOrSuper(parameter[0].getType(), NettyWebsocketRequest.class) && ClassUtil.isDomainOrSuper(parameter[1].getType(), NettyWebsocketMessage.class))) {
                    log.error(String.format("source %s ,illegal parameter", method), new IllegalParameterException("illegal parameter ,Must be in the following format \n{\npublic void methodName(com.sywl.netty.ws.NettyWebsocketRequest request,com.sywl.netty.ws.NettyWebsocketMessage message);\n}"));
                    System.exit(0);
                }
                Websocket websocket = ClassUtil.getAnnotation(method, Websocket.class);
                if (functionHandlerMap.containsKey(websocket.value())) {
                    log.error("IFunctionHandler has duplicated :" + websocket.value(), new IllegalPathDuplicatedException(String.format("%s There are multiple", websocket.value())));
                    System.exit(0);
                }
                NettWebsocketMethod processor = new NettWebsocketMethod(websocket, source, method);
                functionHandlerMap.put(websocket.value(), processor);
            }
        }

        exceptionCatcherByWebsocket = applicationContext.getBean(ExceptionCatcherByWebsocket.class);
        websocketFactory = applicationContext.getBean(WebsocketFactory.class);

        log.info(">>>>>> load websocket method complete . number [{}]", functionHandlerMap.size());
        log.info(">>>>>> load websocket exceptionCatcher [{}]", exceptionCatcherByWebsocket.getClass());
    }

    @Override
    public void canException(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        exceptionCatcherByWebsocket.exception(ctx, cause);
    }
}
